package linc.fun.openai.service.impl;

import cn.hutool.crypto.digest.MD5;
import cn.hutool.http.ContentType;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Maps;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import linc.fun.openai.config.pay.PayConfig;
import linc.fun.openai.constants.ApplicationConstants;
import linc.fun.openai.domain.dto.request.AlipayFace2faceTradeRequest;
import linc.fun.openai.domain.dto.request.AlipayTradePreCreateRequest;
import linc.fun.openai.domain.entity.chat.ChatOrderDO;
import linc.fun.openai.domain.entity.chat.ChatOrderHistoryDO;
import linc.fun.openai.domain.vo.AlipayFace2faceTradeResponse;
import linc.fun.openai.domain.vo.AlipayTradePreCreateResponse;
import linc.fun.openai.enums.ApiTypeEnum;
import linc.fun.openai.enums.ChatOrderStatusEnum;
import linc.fun.openai.exception.BizException;
import linc.fun.openai.mapper.ChatOrderHistoryMapper;
import linc.fun.openai.mapper.ChatOrderMapper;
import linc.fun.openai.service.ChatPayService;
import linc.fun.openai.util.ObjectMapperUtil;
import linc.fun.openai.util.OkHttpClientUtil;
import linc.fun.openai.util.RsaUtil;
import lombok.extern.slf4j.Slf4j;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.Objects;

import static linc.fun.openai.domain.entity.chat.table.Tables.ChatOrder;
import static linc.fun.openai.domain.entity.chat.table.Tables.ChatOrderHistory;

/**
 * @author yqlin
 * @date 2023/5/6 11:30
 * @description
 */
@Slf4j
@Service
public class ChatPayServiceImpl implements ChatPayService {
    @Resource
    private PayConfig payConfig;
    @Resource
    private RsaUtil rsaUtil;
    @Resource
    private ChatOrderMapper chatOrderMapper;
    @Resource
    private ChatOrderHistoryMapper chatOrderHistoryMapper;
    @Resource
    private TransactionTemplate transactionTemplate;

    /**
     * 先将请求参数==>相关参数校验=>RSA加密==>Base加密==>调用go支付中台
     * <p>
     * Go支付中台(服务放国内) 国内查询国外的mysql将近50倍的延迟
     * 当面付Face2facePay
     * 主要流程: Base64 ==> 解码 ==> RSA解密 ==> 反序列化 ==> 验签 ==> 进行支付宝调用
     * <p>
     * <p>
     * Java支付前台(服务放外国)
     * 当面付Face2facePay
     * 主要流程: 请求参数封装=>>验签处理==>转JSON==>RSA加密=>Base64加码==>调用Go支付中台
     */
    @Override
    public AlipayFace2faceTradeResponse face2facePay(AlipayFace2faceTradeRequest req) {
        // 查询可支付订单
        ChatOrderDO order = this.queryCanPayOrder(req);
        // 查询历史订单 或者 调用go支付中台获取支付链接
        return this.queryOrCallPay(order);
    }

    @NotNull
    private AlipayFace2faceTradeResponse queryOrCallPay(ChatOrderDO order) {
        AlipayFace2faceTradeResponse response = new AlipayFace2faceTradeResponse();
        // 如果是待确认，说明已经存在支付链接，就把支付链接展示出来就行
        if (ChatOrderStatusEnum.PENDING_CONFIRMATION.equals(order.getStatus())) {
            ChatOrderHistoryDO orderHistory = chatOrderHistoryMapper.selectOneByQuery(QueryWrapper.create()
                    .where(ChatOrderHistory.OrderId.eq(order.getId()))
            );
            if (Objects.isNull(orderHistory)) {
                throw BizException.DATA_HAS_BEEN_LOOSED;
            }
            response.setTimeoutExpress(payConfig.getTimeoutExpress());
            response.setOrderNo(order.getId().toString());
            response.setQrCode(orderHistory.getPayQrCode());
        } else {
            // 构建支付参数
            String tradePreCreateRequestJson = this.composeAlipayTradePreCreateRequest(order);
            log.info("构建支付请求参数: \n{}", tradePreCreateRequestJson);
            // 请求go支付中台
            response = this.doRequestFace2faceAlipay(tradePreCreateRequestJson);
            // 修改订单状态为已确认
            this.confirmOrder(order, response);
        }
        return response;
    }


    public void confirmOrder(ChatOrderDO order, AlipayFace2faceTradeResponse response) {

        ChatOrderHistoryDO orderHistory = chatOrderHistoryMapper.selectOneByQuery(QueryWrapper.create()
                .where(ChatOrderHistory.OrderId.eq(order.getId())));
        if (Objects.isNull(orderHistory)) {
            throw BizException.DATA_HAS_BEEN_LOOSED;
        }

        order.setStatus(ChatOrderStatusEnum.PENDING_CONFIRMATION);
        order.setUpdateTime(LocalDateTime.now());
        order.setRemark(ApplicationConstants.CHAT_ORDER_PURCHASE_SUBJECT);
        orderHistory.setOrderStatus(order.getStatus());
        orderHistory.setRemark(ApplicationConstants.CHAT_ORDER_PURCHASE_SUBJECT);
        orderHistory.setPayQrCode(response.getQrCode());
        order.setUpdateTime(LocalDateTime.now());

        Boolean ok = transactionTemplate.execute(status -> {
            try {
                chatOrderMapper.update(order);
                chatOrderHistoryMapper.update(orderHistory);
            } catch (Exception e) {
                status.setRollbackOnly();
                log.error("订单支付更新异常", e);
                return false;
            }
            return true;
        });
        if (Boolean.FALSE.equals(ok)) {
            throw BizException.of("获取订单支付异常");
        }

    }

    /**
     * 查询可支付订单
     */
    @NotNull
    private ChatOrderDO queryCanPayOrder(AlipayFace2faceTradeRequest req) {
        // 查询待付款和待确认的
        ChatOrderDO order = chatOrderMapper.selectOneByQuery(QueryWrapper.create()
                .where(ChatOrder.Id.eq(req.getOrderNo()))
                .and(ChatOrder.Status.in(ChatOrderStatusEnum.PENDING_PAYMENT, ChatOrderStatusEnum.PENDING_CONFIRMATION))
        );
        if (Objects.isNull(order)) {
            throw BizException.of("当前订单不存在或已完成付款");
        }
        return order;
    }

    /**
     * 构建支付参数
     */
    private String composeAlipayTradePreCreateRequest(ChatOrderDO order) {
        AlipayTradePreCreateRequest tradePreCreateRequest = new AlipayTradePreCreateRequest();
        MD5 md5 = MD5.create();
        tradePreCreateRequest.setSubject(ApplicationConstants.CHAT_ORDER_PURCHASE_SUBJECT);
        tradePreCreateRequest.setTotalAmount(order.getTotalAmount().toString());
        tradePreCreateRequest.setOutTradeNo(order.getId().toString());
        tradePreCreateRequest.setTimeoutExpress(payConfig.getTimeoutExpress());
        tradePreCreateRequest.setSignature(payConfig.getSignature() + md5.digestHex(order.getId().toString()));
        return ObjectMapperUtil.toJson(tradePreCreateRequest);
    }

    /**
     * 请求go支付中台
     */
    @NotNull
    private AlipayFace2faceTradeResponse doRequestFace2faceAlipay(String tradePreCreateRequestJson) {
        // RSA加密 + base64加密
        String encode = rsaUtil.encrypt(tradePreCreateRequestJson);
        Map<String, Object> body = Maps.newHashMap();
        body.put("base64", encode);
        OkHttpClient okHttpClient = OkHttpClientUtil.getInstance(ApiTypeEnum.AlipayGo,
                60000, 60000, 60000, null);
        Request request = new Request.Builder()
                .url(payConfig.getAlipayFace2faceUrl())
                .post(RequestBody.create(ObjectMapperUtil.toJson(body), MediaType.parse(ContentType.JSON.getValue())))
                .build();
        try (Response response = okHttpClient.newCall(request).execute()) {
            if (Objects.nonNull(response.body())) {
                String responseBody = response.body().string();
                log.info("调用Go支付中台返回值:\n {}", responseBody);
                AlipayTradePreCreateResponse alipayTradePreCreateResponse = JSON.parseObject(responseBody, AlipayTradePreCreateResponse.class);
                if (alipayTradePreCreateResponse.getSuccess()) {
                    AlipayTradePreCreateResponse.Data data = alipayTradePreCreateResponse.getData();
                    AlipayFace2faceTradeResponse tradeResponse = new AlipayFace2faceTradeResponse();
                    tradeResponse.setOrderNo(data.getOrderNo());
                    tradeResponse.setQrCode(data.getQrCode());
                    tradeResponse.setTimeoutExpress(payConfig.getTimeoutExpress());
                    return tradeResponse;
                } else {
                    throw BizException.of("获取支付二维码异常，请稍后再试");
                }
            }
        } catch (Exception e) {
            log.error("支付异常，异常信息", e);
            throw BizException.of("调用支付异常，请联系管理员");
        }
        throw BizException.of("获取支付二维码异常，请稍后再试");
    }


}
